﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using ProtocoloPropietario;
using System.Threading;
using System.IO;

namespace Servidor
{
    class SharedState
    {
        public bool ContinueProcess;
        public int NumberOfClients;
        public AutoResetEvent Ev;
    }

    class ConexionServidor
    {
        private IPAddress _ipMultiCastServidor;
        private int _numPuertoMulti;
        private static Servidor _servidor;
        private Socket _socCliente;
        private Socket _socServidor;
        private IPAddress _ipLocal;

        private static SharedState SharedStateObj;

        public ConexionServidor(Servidor serv, IPAddress ipMultiCast, int puertoMulti)
        {
            _servidor = serv;
            _ipMultiCastServidor = ipMultiCast;
            _numPuertoMulti = puertoMulti;
            _ipLocal = LocalIPAddress();
            _socCliente = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socServidor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            MensajeProtocolo msg = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_ACTUALIZAR_BANDA_ANCHA, 0, "");

            EnviarPaqueteUDP(msg);
        }

        public void StartListeningTCP(Object stateInfo)
        {

            SharedStateObj = new SharedState();
            SharedStateObj.ContinueProcess = true;
            SharedStateObj.NumberOfClients = 0;
            SharedStateObj.Ev = new AutoResetEvent(false);

            TcpListener listener = new TcpListener(_servidor.IpServidor, _servidor.NumPuertoServidor);
            try
            {
                listener.Start();

                int TestingCycle = 10000;
                int ClientNbr = 0;

                Console.WriteLine("Waiting for a connection...");
                while (TestingCycle > 0)
                {

                    TcpClient handler = listener.AcceptTcpClient();

                    if (handler != null)
                    {
                        Console.WriteLine("Client#{0} accepted!", ++ClientNbr);

                        ClientHandlerTcp client = new ClientHandlerTcp(handler);

                        Interlocked.Increment(ref SharedStateObj.NumberOfClients);

                        ThreadPool.QueueUserWorkItem(new WaitCallback(client.Process), SharedStateObj);

                        --TestingCycle;
                    }
                    else
                        break;
                }

                listener.Stop();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            // Stop and wait all client connections to end
            SharedStateObj.ContinueProcess = false;
            SharedStateObj.Ev.WaitOne();

            Console.WriteLine("\nHit enter to continue...");
            Console.Read();
        }

        public void RecibirNotificacionesUDP(Object stateInfo)
        {
            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            IPEndPoint ipep = new IPEndPoint(_ipLocal, _numPuertoMulti);
            s.Bind(ipep);

            s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_ipMultiCastServidor, IPAddress.Any));

            while (true)
            {
                byte[] paquete = new byte[1000];
                Console.WriteLine("Waiting for data...");
                s.Receive(paquete);

                MensajeProtocolo msjP = MensajeProtocolo.ObtenerMensajeProtocolo(paquete);
                switch (msjP.Cmd)
                {
                    case Protocolo.Comando.CLIENTE_CONEXION:
                        ThreadPool.QueueUserWorkItem(new WaitCallback(ConectarCliente), msjP);
                        break;
                    case Protocolo.Comando.SERVIDOR_CONEXION_SUBIDA:
                        ThreadPool.QueueUserWorkItem(new WaitCallback(ConectarServidorSubida), msjP);
                        break;
                    case Protocolo.Comando.SERVIDOR_ACTUALIZAR_BANDA_ANCHA:
                        ThreadPool.QueueUserWorkItem(new WaitCallback(ActualizarBandaAnchaServidores), msjP);
                        break;
                    default:
                        break;
                }

            }
        }

        public void EnviarPaqueteUDP(MensajeProtocolo msg)
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_ipMultiCastServidor));
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 2);

            IPEndPoint ipep = new IPEndPoint(_ipMultiCastServidor, _numPuertoMulti);
            Console.WriteLine("Connecting...");
            socket.Connect(ipep);

            byte[] paquete = msg.ConvertirAByte();

            socket.Send(paquete, paquete.Length, SocketFlags.None);
            socket.Close();
        }

        private void ConectarServidorSubida(Object o)
        {            
            List<string> strMensaje = ((MensajeProtocolo)o).InfoDatos();
            Cliente cliente = new Cliente(strMensaje[0], Int32.Parse(strMensaje[1]));

            //Fijarse si es el mismo servidor
            if (!cliente.Ip.Equals(_servidor.IpServidor) && !cliente.Puerto.Equals(_servidor.NumPuertoServidor))
            {
                TcpClient tcpClient = new TcpClient();
                TransferFile tfile = new TransferFile(cliente, tcpClient);
                tfile.RecibirSubidaArchivoServidor(strMensaje[2]);
            }

        }

        private void ActualizarBandaAnchaServidores(Object o)
        {
            MensajeProtocolo msgProtocol = (MensajeProtocolo)o;

            switch (msgProtocol.Header)
            {
                case Protocolo.Header.REQ:
                    List<string> listaDatos = msgProtocol.InfoDatos();

                    //El recurso Servidores del Programa es compartido por varios procesos por lo tanto hay que manejar la concurrencia al recurso
                    string ipTCPServidorReq = listaDatos[0];
                    string puertoTCPServidorReq = listaDatos[1];
                    string ipTCPServidorClienteReq = listaDatos[2];
                    string puertoServidorClienteReq = listaDatos[3];
                    int anchoBandaCliente = Int32.Parse(listaDatos[4]);
                    int anchoBandaTotal = Int32.Parse(listaDatos[5]);
                    int anchoBandaOcupada = Int32.Parse(listaDatos[6]);

                    AnchoDeBanda anchoDeBanda = new AnchoDeBanda(anchoBandaTotal);
                    anchoDeBanda.ActualizarAncho(anchoBandaOcupada);

                    Servidor servidorReq = null;
                    string servidorKey = ipTCPServidorReq + "_" + puertoTCPServidorReq;
                    string myServidorKey = _servidor.IpCliente.ToString() + "_" + _servidor.NumPuertoCliente;

                    if (!myServidorKey.Equals(servidorKey))
                    {
                        if (Program.servidores.ContainsKey(servidorKey))
                        {
                            servidorReq = Program.servidores[servidorKey];
                            servidorReq.AnchoBandaPorCliente = anchoBandaCliente;
                            servidorReq.AnchoDeBanda = anchoDeBanda;

                            Console.WriteLine("Actualizando Lista Servidores: Servido Existente");
                        }
                        else
                        {
                            servidorReq = new Servidor(IPAddress.Parse(ipTCPServidorClienteReq), Int32.Parse(puertoServidorClienteReq), IPAddress.Parse(ipTCPServidorReq), Int32.Parse(puertoTCPServidorReq), anchoBandaCliente, anchoDeBanda);
                            Program.servidores.Add(servidorKey, servidorReq);

                            Console.WriteLine("Actualizando Lista Servidores: Servido Nuevo");
                        }

                        Console.WriteLine("\tServidorCliente TCP IP: " + ipTCPServidorClienteReq + " / PuertoServidorC: " + puertoServidorClienteReq);
                        Console.WriteLine("\tServidor TCP IP: " + ipTCPServidorReq + " / Puerto Servidor: " + puertoTCPServidorReq);
                        Console.WriteLine("\tAncho de Banda Total: " + anchoBandaTotal);
                        Console.WriteLine("\tAncho de Banda Ocupado: " + anchoBandaOcupada);
                        Console.WriteLine("\tAncho de Banda por Cliente: " + anchoBandaCliente);
                    }
                    break;
                case Protocolo.Header.RES:
                    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_ipMultiCastServidor));
                    socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 2);

                    IPEndPoint ipep = new IPEndPoint(_ipMultiCastServidor, _numPuertoMulti);
                    Console.WriteLine("Connecting...");
                    socket.Connect(ipep);

                    string strMsj = _servidor.IpServidor.ToString() + Protocolo.SEPARADOR + _servidor.NumPuertoServidor + Protocolo.SEPARADOR + _servidor.IpCliente.ToString() + Protocolo.SEPARADOR + _servidor.NumPuertoServidor + Protocolo.SEPARADOR + _servidor.AnchoBandaPorCliente
                        + Protocolo.SEPARADOR + _servidor.AnchoDeBanda.Total + Protocolo.SEPARADOR + _servidor.AnchoDeBanda.Ocupado;
                    MensajeProtocolo msg = new MensajeProtocolo(Protocolo.Header.REQ, Protocolo.Comando.SERVIDOR_ACTUALIZAR_BANDA_ANCHA, strMsj.Length, strMsj);

                    byte[] b = msg.ConvertirAByte();
                    socket.Send(b, b.Length, SocketFlags.None);
                    socket.Close();
                    break;
                default:
                    break;
            }
        }

        private void ConectarCliente(Object o)
        {
            Dictionary<string, Servidor>.ValueCollection coleccionValores = Program.servidores.Values;
            Servidor mejorServidor = _servidor;
            int masDisponible = _servidor.AnchoDeBanda.Total - _servidor.AnchoDeBanda.Ocupado;
            foreach (Servidor s in coleccionValores)
            {
                int bandaLibre = s.AnchoDeBanda.Total - s.AnchoDeBanda.Ocupado;
                if ((bandaLibre) > masDisponible)
                {
                    mejorServidor = s;
                    masDisponible = bandaLibre;
                }
            }
            if (mejorServidor.Equals(_servidor))
            {
                List<string> strMensaje = ((MensajeProtocolo)o).InfoDatos();

                Cliente cliente = new Cliente(strMensaje[0], Int32.Parse(strMensaje[1]));

                IPEndPoint ipEndCliente = new IPEndPoint(cliente.Ip, cliente.Puerto);
                Socket socCliente = new Socket(ipEndCliente.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                Socket socServidor = new Socket(ipEndCliente.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                socCliente.Connect(ipEndCliente);
                bool otorgo = false;

                Monitor.Enter(_servidor);
                try
                {
                    otorgo = _servidor.OtorgarAnchoDeBanda();
                }
                finally
                {
                    Monitor.Exit(_servidor);
                }


                if (otorgo){
                    string msj = _servidor.IpCliente.ToString() + Protocolo.SEPARADOR + _servidor.NumPuertoCliente + Protocolo.SEPARADOR + _servidor.AnchoBandaPorCliente;
                    MensajeProtocolo msjRes = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_CONEXION, msj.Length, msj);

                    byte[] paqueteRes = msjRes.ConvertirAByte();
                    socCliente.Send(paqueteRes, paqueteRes.Length, SocketFlags.None);
                    socCliente.Close();

                    IPEndPoint ipEndSer = new IPEndPoint(_servidor.IpCliente, _servidor.NumPuertoCliente);
                    socServidor.Bind(ipEndSer);
                    socServidor.Listen(5);
                    socCliente = socServidor.Accept();

                    byte[] paqueteReq = new byte[100];
                    socCliente.Receive(paqueteReq);

                    MensajeProtocolo msjReqCliente = MensajeProtocolo.ObtenerMensajeProtocolo(paqueteReq);
                    TransferFile transfFlie = new TransferFile(_servidor, cliente, socCliente);
                    socServidor.Close();
                    switch (msjReqCliente.Cmd)
                    {
                        case Protocolo.Comando.CLIENTE_BAJADA_INICIO:
                            transfFlie.Bajar(msjReqCliente);
                            break;
                        case Protocolo.Comando.CLIENTE_BAJADA_POS:
                            transfFlie.Bajar(msjReqCliente);
                            break;
                        case Protocolo.Comando.CLIENTE_SUBIDA:
                            transfFlie.Subir(msjReqCliente);
                            break;
                        default:
                            break;
                    }
                    _servidor.QuitarAnchoDeBanda();
                }
                else
                {
                    string msj = _servidor.IpCliente.ToString() + Protocolo.SEPARADOR + _servidor.NumPuertoCliente + Protocolo.SEPARADOR + _servidor.AnchoBandaPorCliente;
                    MensajeProtocolo msjRes = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.DORMIR, msj.Length, msj);

                    byte[] paqueteRes = msjRes.ConvertirAByte();
                    socCliente.Send(paqueteRes, paqueteRes.Length, SocketFlags.None);

                }
                socCliente.Close();
            }
        }

        private void AceptarTCP(Cliente cli)
        {
            IPEndPoint ipEndAEscuchar = new IPEndPoint(cli.Ip, cli.Puerto);
            _socServidor.Bind(ipEndAEscuchar);
            _socServidor.Listen(5);
            _socCliente = _socServidor.Accept();
        }

        private IPAddress LocalIPAddress()
        {
            IPHostEntry host;
            IPAddress ipLocal = null;
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    ipLocal = ip;
                }

            }
            return ipLocal;
        }

    }
}
